package org.tau.workshop2011.widgets

import javax.swing.event.ChangeEvent
import javax.swing.event.ChangeListener
import javax.swing.event.DocumentEvent
import javax.swing.event.DocumentListener
import org.tau.workshop2011.codegen.{DSLProperty}
import org.tau.workshop2011.expressions.{HAlign, VAlign}
import java.lang.AssertionError
import javax.swing._
import java.beans.{PropertyChangeEvent, PropertyChangeListener}

trait NativeWidgetWrapper {
  def getWidth: Option[DSLProperty[Int]]
  def getHeight: Option[DSLProperty[Int]]
  def getNativeWidget: JComponent
}

object NativeWidgetMaker {

  def MakeLabel(props: Map[String, DSLProperty[_]], width: Option[DSLProperty[Int]], height: Option[DSLProperty[Int]]): BaseWidget[JLabel] = {
    new BaseWidget(new JLabel, props, width, height) {

      val textBind = new NativePropertyBinder(props.getOrElse("text", null).asInstanceOf[DSLProperty[String]]) {
        def get = realWidget.getText

        protected def realSet(newVal: String) { realWidget.setText(newVal) }
      }

      val halignBind = new NativePropertyBinder(props.getOrElse("halign", null).asInstanceOf[DSLProperty[HAlign]]) {
        def get = HAlign fromSwing realWidget.getHorizontalAlignment

        protected def realSet(newVal: HAlign) { realWidget.setHorizontalAlignment(HAlign toSwing newVal) }
      }

      val valignBind = new NativePropertyBinder(props.getOrElse("valign", null).asInstanceOf[DSLProperty[VAlign]]) {
        def get = VAlign fromSwing realWidget.getVerticalAlignment

        protected def realSet(newVal: VAlign) { realWidget.setVerticalAlignment(VAlign toSwing newVal) }
      }
    }
  }

  /* TODO: Text alignment isn't supported. Find a better widget */
  def MakeTextBox(props: Map[String, DSLProperty[_]], width: Option[DSLProperty[Int]], height: Option[DSLProperty[Int]]): BaseWidget[JTextArea] = {
    new BaseWidget(new JTextArea, props, width, height) {

      @scala.volatile private var textLock = false;
      /* This is a property editable by the user and therefore we need a
       * listener for it */
      val textBind = new NativePropertyBinder(props.getOrElse("text", null).asInstanceOf[DSLProperty[String]]) {
        def get = realWidget.getText

        protected def realSet(newVal: String) { if (! textLock) realWidget.setText(newVal) }
      }
      if (textBind.property != null)
        realWidget.setEditable(! props("text").isReadOnly)

      realWidget.getDocument.addDocumentListener(new DocumentListener() {
        def MyOnChange() {
          textLock = true
          textBind.callOnNativeChange();
          textLock = false
        }

        def insertUpdate(e: DocumentEvent) { MyOnChange() }

        def removeUpdate(e: DocumentEvent) { MyOnChange() }

        def changedUpdate(e: DocumentEvent) { MyOnChange() }
      });
    }
  }

  def MakeButton(props: Map[String, DSLProperty[_]], width: Option[DSLProperty[Int]], height: Option[DSLProperty[Int]]): BaseWidget[JButton] = {
    new BaseWidget(new JButton, props, width, height) {

      val pressed = false;

      val textBind = new NativePropertyBinder(props.getOrElse("text", null).asInstanceOf[DSLProperty[String]]) {
        def get = realWidget.getText

        protected def realSet(newVal: String) { realWidget.setText(newVal) }
      }

      val imageBind = new NativePropertyBinder(props.getOrElse("image", null).asInstanceOf[DSLProperty[String]]) {
        var iconPath = ""

        def get = iconPath

        protected def realSet(newVal: String) {
          iconPath = newVal
          if (newVal.isEmpty)
            realWidget.setIcon(new ImageIcon())
          else
            realWidget.setIcon(new ImageIcon(newVal))
        }
      }


      /**
       * Hooking up the checked property, which corresponds to whether the
       * button is pressed or not, requires setting up a custom listener to
       * check the state of the ButtonModel. We can't be notified of that
       * directly, and unlike other properties, this one can be changed be the
       * user directly
       *
       * See more information on
       * http://stackoverflow.com/questions/7505279/test-if-a-javax-swing-jbutton-is-pressed-down
       */
      val checkedBind = new NativePropertyBinder(props.getOrElse("checked", null).asInstanceOf[DSLProperty[Boolean]]) {
        def get = realWidget.getModel.isPressed

        protected def realSet(newVal: Boolean) { realWidget.getModel.setPressed(newVal) }
      }

      if (checkedBind.property != null && props("checked").isReadOnly) {
        enabledBind.disconnectExternalChanges()
        realWidget.setEnabled(false)
      }

      realWidget.getModel.addChangeListener(new ChangeListener {
        var checked = false;

        def stateChanged(e: ChangeEvent) {
          val newChecked = realWidget.getModel.isPressed
          if (newChecked != checked) {
            checkedBind.callOnNativeChange()
            checked = newChecked
          }
        }
      })

      val halignBind = new NativePropertyBinder(props.getOrElse("halign", null).asInstanceOf[DSLProperty[HAlign]]) {
        def get = HAlign fromSwing realWidget.getHorizontalAlignment

        protected def realSet(newVal: HAlign) { realWidget.setHorizontalAlignment(HAlign toSwing newVal) }
      }

      val valignBind = new NativePropertyBinder(props.getOrElse("valign", null).asInstanceOf[DSLProperty[VAlign]]) {
        def get = VAlign fromSwing realWidget.getVerticalAlignment

        protected def realSet(newVal: VAlign) { realWidget.setVerticalAlignment(VAlign toSwing newVal) }
      }
    }
  }

  /* The checkboxes that the specs describe are just the box itself without any
   * text or anything else */
  def MakeCheckBox(props: Map[String, DSLProperty[_]], width: Option[DSLProperty[Int]], height: Option[DSLProperty[Int]]): BaseWidget[JCheckBox] = {
    new BaseWidget(new JCheckBox, props, width, height) {

      val checkedBind = new NativePropertyBinder(props.getOrElse("checked", null).asInstanceOf[DSLProperty[Boolean]]) {
        def get = realWidget.isSelected

        protected def realSet(newVal: Boolean) {
          realWidget.setSelected(newVal)
        }
      }

      if (checkedBind.property != null && props("checked").isReadOnly) {
        enabledBind.disconnectExternalChanges()
        realWidget.setEnabled(false)
      }

      realWidget.addItemListener(new java.awt.event.ItemListener {
        var selected = realWidget.isSelected

        def itemStateChanged(e: java.awt.event.ItemEvent) {
          if (e.getStateChange == java.awt.event.ItemEvent.DESELECTED && selected) {
            selected = false
            checkedBind.callOnNativeChange()
          }
          else if (e.getStateChange == java.awt.event.ItemEvent.SELECTED && ! selected) {
            selected = true
            checkedBind.callOnNativeChange()
          }
        }
      })
    }
  }

  /* The radio buttons that the specs describe are just the button itself
   * without any text or anything else */
  def MakeRadioButton(props: Map[String, DSLProperty[_]], width: Option[DSLProperty[Int]], height: Option[DSLProperty[Int]]): BaseWidget[JRadioButton] = {
    new BaseWidget(new JRadioButton, props, width, height) {

      val checkedBind = new NativePropertyBinder(props.getOrElse("checked", null).asInstanceOf[DSLProperty[Boolean]]) {
        def get = realWidget.isSelected

        protected def realSet(newVal: Boolean) { realWidget.setSelected(newVal) }
      }

      if (checkedBind.property != null && props("checked").isReadOnly) {
        enabledBind.disconnectExternalChanges()
        realWidget.setEnabled(false)
      }

      realWidget.addItemListener(new java.awt.event.ItemListener {
        var selected = realWidget.isSelected;

        def itemStateChanged(e: java.awt.event.ItemEvent) {
          if (e.getStateChange == java.awt.event.ItemEvent.DESELECTED && selected) {
            selected = false
            checkedBind.callOnNativeChange()
          }
          else if (e.getStateChange == java.awt.event.ItemEvent.SELECTED && !selected) {
            selected = true
            checkedBind.callOnNativeChange()
          }
        }
      })
    }
  }

  def MakeImage(props: Map[String, DSLProperty[_]], width: Option[DSLProperty[Int]], height: Option[DSLProperty[Int]]): BaseWidget[JLabel] = {
    new BaseWidget(new JLabel, props, width, height) {
      val imageBind = new NativePropertyBinder(props.getOrElse("image", null).asInstanceOf[DSLProperty[String]]) {
        var iconPath = ""

        def get = iconPath

        protected def realSet(newVal: String) {
          iconPath = newVal
          if (newVal.isEmpty)
            realWidget.setIcon(new ImageIcon())
          else
            realWidget.setIcon(new ImageIcon(newVal))
        }
      }
    }
  }

  def MakeComboBox(props: Map[String, DSLProperty[_]], width: Option[DSLProperty[Int]], height: Option[DSLProperty[Int]]): BaseWidget[JComboBox] = {
    new BaseWidget(new JComboBox, props, width, height) {

      /* In order to avoid problems in combo-boxes when we have duplicate
       * strings, we must wrap the strings in objects */
      class ComboBoxOption(value: String) {
        override def toString = value
      }

      /* A string containing all the values to be presented for selection,
       * seperated by '|' */
      val textBind = new NativePropertyBinder(props.getOrElse("text", null).asInstanceOf[DSLProperty[String]]) {
        var options_text = "";

        def get = options_text

        protected def realSet(newVal: String) {
          realWidget.removeAllItems()
          for (str <- newVal.split('|'))
            realWidget.addItem(new ComboBoxOption(str))
        }
      }

      /* Index of the selected item, 0 is the first, -1 if nothing is selected */
      val valueBind = new NativePropertyBinder(props.getOrElse("value", null).asInstanceOf[DSLProperty[Int]]) {
        def get = realWidget.getSelectedIndex

        protected def realSet(newVal: Int) { realWidget.setSelectedIndex(newVal) }
      }

      if (valueBind.property != null && props("value").isReadOnly) {
        realWidget.setEditable(false)
      }

      realWidget.addActionListener(new java.awt.event.ActionListener {
        var old_selected = realWidget.getSelectedIndex;

        override def actionPerformed(e: java.awt.event.ActionEvent) {
          if (old_selected != realWidget.getSelectedIndex) {
            old_selected = realWidget.getSelectedIndex
            valueBind.callOnNativeChange()
          }
        }
      })
    }
  }

  def MakeSlider(props: Map[String, DSLProperty[_]], width: Option[DSLProperty[Int]], height: Option[DSLProperty[Int]]): BaseWidget[JSlider] = {
    new BaseWidget(new JSlider, props, width, height) {

      val minvalueBind = new NativePropertyBinder(props.getOrElse("minvalue", null).asInstanceOf[DSLProperty[Int]]) {
        def get = realWidget.getMinimum

        protected def realSet(newVal: Int) { realWidget.setMinimum(newVal) }
      }

      val maxvalueBind = new NativePropertyBinder(props.getOrElse("maxvalue", null).asInstanceOf[DSLProperty[Int]]) {
        def get = realWidget.getMaximum

        protected def realSet(newVal: Int) { realWidget.setMaximum(newVal) }
      }

      val valueBind = new NativePropertyBinder(props.getOrElse("value", null).asInstanceOf[DSLProperty[Int]]) {
        def get = realWidget.getValue

        protected def realSet(newVal: Int) { realWidget.setValue(newVal) }
      }

      if (valueBind.property != null && props("value").isReadOnly) {
        enabledBind.disconnectExternalChanges()
        realWidget.setEnabled(false)
      }

      realWidget.addChangeListener(new ChangeListener {
        var old_val = realWidget.getValue

        def stateChanged(e: ChangeEvent) {
          if (old_val != realWidget.getValue) {
            old_val = realWidget.getValue;
            valueBind.callOnNativeChange()
          }
        }
      })
    }
  }

  def MakeSpacer(props: Map[String, DSLProperty[_]], width: Option[DSLProperty[Int]], height: Option[DSLProperty[Int]]): BaseWidget[JPanel] = {
    new BaseWidget(new JPanel(), props, width, height)
  }

  def MakeWidget(wType: String, props: Map[String, DSLProperty[_]], width: Option[DSLProperty[Int]], height: Option[DSLProperty[Int]]): BaseWidget[_] = {
    wType match {
      case "label" => MakeLabel(props, width, height)
      case "textbox" => MakeTextBox(props, width, height)
      case "button" => MakeButton(props, width, height)
      case "checkbox" => MakeCheckBox(props, width, height)
      case "radio" => MakeRadioButton(props, width, height)
      case "image" => MakeImage(props, width, height)
      case "comboxbox" => MakeComboBox(props, width, height)
      case "slider" => MakeSlider(props, width, height)
      case "" => MakeSpacer(props, width, height)
      case _ => throw new AssertionError()
    }
  }
}
